package com.atguigu.tingshu.account.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.atguigu.tingshu.account.mapper.UserAccountDetailMapper;
import com.atguigu.tingshu.account.mapper.UserAccountMapper;
import com.atguigu.tingshu.account.mapper.UserAccountPayPasswordMapper;
import com.atguigu.tingshu.account.service.UserAccountService;
import com.atguigu.tingshu.common.execption.GuiguException;
import com.atguigu.tingshu.common.util.AuthContextHolder;
import com.atguigu.tingshu.common.util.IdWorker;
import com.atguigu.tingshu.model.account.UserAccount;
import com.atguigu.tingshu.model.account.UserAccountDetail;
import com.atguigu.tingshu.model.account.UserAccountPayPassword;
import com.atguigu.tingshu.vo.payment.PayVo;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.client.RestTemplate;

import java.math.BigDecimal;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
@Transactional(rollbackFor = Exception.class)
@SuppressWarnings({"unchecked", "rawtypes"})
public class UserAccountServiceImpl extends ServiceImpl<UserAccountMapper, UserAccount> implements UserAccountService {

    @Autowired
    private UserAccountMapper userAccountMapper;
    @Autowired
    private UserAccountDetailMapper userAccountDetailMapper;
    @Autowired
    private IdWorker idWorker;
    @Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RedissonClient redissonClient;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private UserAccountPayPasswordMapper userAccountPayPasswordMapper;

    @Override
    public void accountInit(Long userId) {
        // 查询用户是否存在账户
        UserAccount userAccount =
                getOne(new LambdaQueryWrapper<UserAccount>()
                        .eq(UserAccount::getUserId, userId));
        // 若存在 直接结束
        if (userAccount != null) {
            return;
        }
        // 初始化DO
        userAccount = new UserAccount();
        // 设置属性
        userAccount.setUserId(userId);
        // 写库
        save(userAccount);
    }

    /**
     * 分页查询用户消费记录
     *
     * @return
     */
    @Override
    public Object findUserConsumePage(Integer page, Integer size) {
        // 交易类型：1201-充值 1202-锁定 1203-解锁 1204-消费
        return userAccountDetailMapper.selectPage(
                new Page<>(page, size),
                new LambdaQueryWrapper<UserAccountDetail>()
                        .eq(UserAccountDetail::getUserId, AuthContextHolder.getUserId())
                        .eq(UserAccountDetail::getTradeType, "1204"));
    }

    /**
     * 分页查询用户充值记录
     *
     * @return
     */
    @Override
    public Object findUserRechargePage(Integer page, Integer size) {
        // 交易类型：1201-充值 1202-锁定 1203-解锁 1204-消费
        return userAccountDetailMapper.selectPage(
                new Page<>(page, size),
                new LambdaQueryWrapper<UserAccountDetail>()
                        .eq(UserAccountDetail::getUserId, AuthContextHolder.getUserId())
                        .eq(UserAccountDetail::getTradeType, "1201"));
    }

    /**
     * 获取余额支付的支付url
     *
     * @param params
     * @return
     */
    @Override
    public Object getPayInfo(Map<String, String> params) {
        // 生成预支付交易
        UserAccountDetail userAccountDetail = new UserAccountDetail();
        userAccountDetail.setUserId(Long.valueOf(params.get("userId")));
        userAccountDetail.setTitle(params.get("description"));
        // 交易类型：1201-充值 1202-锁定 1203-解锁 1204-消费
        userAccountDetail.setTradeType("1204");
        userAccountDetail.setAmount(new BigDecimal(params.get("price")));
        userAccountDetail.setOrderNo(params.get("orderNo"));
        // 生成预交易的流水号
        Long prepayId = idWorker.nextId();
        // 存储到redis
        redisTemplate.opsForValue().set(
                "PrepayId_" + prepayId,
                JSONObject.toJSONString(userAccountDetail),
                2,
                TimeUnit.HOURS);
        // 返回付款url -->  能通过url找到当前预交易
        String url = "http://localhost/api/account/userAccount/accountPay?key=" + prepayId;
        return url;
    }

    /**
     * 账户余额支付 -- 校验即将校验订单是否存在
     * 交易存在 --> 携带orderNo  --> 跳转到支付页面
     * 交易不存在 --> 超时取消 / 取消 / 已支付的情况 -- 抛异常
     * 从redis中存储的预交易流水中获取
     * redis.key = "PrepayId_" + prepayId
     * redis.value = userAccountDetail
     *
     * @param key
     * @return
     */
    @Override
    public Object accountPay(String key) {
        String order = (String) redisTemplate.opsForValue().get("PrepayId_" + key);
        if (StringUtils.isNotEmpty(order)) {
            // 反序列化
            Map<String, String> result = JSONObject.parseObject(order, Map.class);
            // 返回
            return result;
        }
        throw new GuiguException(201, "交易不存在");
    }


    /**
     * 提交支付，付款前需校验 -- 一笔流水只能被支付一次
     * 1. 单笔流水被多人单次付款 - 并发支付
     * 2. 单笔流水被单人多次付款 - 重复支付
     * AfterPaid：
     * 同步告知用户支付信息
     * 异步feign调用相关微服务修改相应数据
     *
     * @param payVo
     * @return
     */
    @Override
    public void pay(PayVo payVo) {
        // 参数校验
        if (payVo == null ||
                payVo.getPassword() == null ||
                payVo.getPrepayId() == null) {
            throw new GuiguException(201, "参数错误");
        }
        // 从redis获取预交易信息
        String prepayOrder = (String) redisTemplate.opsForValue()
                .get("PrepayId_" + payVo.getPrepayId());
        // 反序列话
        UserAccountDetail userAccountDetail = JSONObject.parseObject(prepayOrder, UserAccountDetail.class);
        // 获取orderNo
        String orderNo = userAccountDetail.getOrderNo();
        // 获取锁: 防止重复支付 & 并发支付
        RLock lock = redissonClient.getLock("Pay_" + orderNo);
        // 删除预交易锁的标识位
        boolean flag = false;
        // 加锁
        if (lock.tryLock()) {
            // 加锁成功 尝试支付
            try {
                // 校验该订单是否未支付 -- 预支付-存在redis / 支付后-写入DB
                Long count = userAccountDetailMapper.selectCount(
                        new LambdaQueryWrapper<UserAccountDetail>()
                                .eq(UserAccountDetail::getOrderNo, orderNo)
                                .eq(UserAccountDetail::getUserId, userAccountDetail.getUserId()));
                // DB有 -- 已被支付
                if (count > 0) {
                    throw new GuiguException(201, "订单已支付");
                }
                // 校验账户是否有初始化支付密码
                UserAccountPayPassword userAccountPayPassword =
                        userAccountPayPasswordMapper.selectOne(
                                new LambdaQueryWrapper<UserAccountPayPassword>()
                                        .eq(UserAccountPayPassword::getUserId, AuthContextHolder.getUserId()));
                if (userAccountPayPassword == null) {
                    throw new GuiguException(201, "账户未初始化");
                }
                // 校验密码
//                if (!BCrypt.checkpw(payVo.getPassword(),
//                        userAccountPayPassword.getPassword())) {
//                    throw new GuiguException(201, "支付密码错误");
//                }
                if (!payVo.getPassword().equals(payVo.getPassword())){
                    throw new GuiguException(201, "支付密码错误");
                }
                // 扣款
                int affectedRow = userAccountMapper.updateAccountAvailableAmount(
                        AuthContextHolder.getUserId(),
                        userAccountDetail.getAmount());
                // 校验扣款结果 -- 余额
                if (affectedRow <= 0 ){
                    throw new GuiguException(201, "余额不足");
                }
                // 记录交易详情
                userAccountDetailMapper.insert(userAccountDetail);
                // 标识交易完成 flag = true
                flag = true;
            } catch (Exception e) {
                throw e;
            } finally {
                // 放锁
                lock.unlock();
                // 判断标识位 删除redis中预交易的锁
                if (flag) {
                    // 删除redis中的锁
                    redisTemplate.delete("PrepayId_" + payVo.getPrepayId());
                    // 异步通知service-payment支付结果
                    CompletableFuture.runAsync(() -> {
                        // 获取通知地址
                        String notifyUrl = userAccountDetail.getNotifyUrl();
                        // 包装请求参数
                        HttpEntity httpEntity = new HttpEntity(userAccountDetail);
                        // 发起请求 获取结果
                        ResponseEntity<String> exchange =
                                restTemplate.exchange(notifyUrl, HttpMethod.POST, httpEntity, String.class);
                        // 交易通知结果
                        if (!exchange.getBody().equals("SUCCESS")){
                            throw new GuiguException(201, "通知失败");
                        }
                    }).whenCompleteAsync((a, b) -> { // 处理结果
                        // 出异常的场合
                        if (b != null) {
                            // --> 重复通知
                            System.out.println("");
                        }
                    });
                }
            }
        }
        // 抢锁失败的场合
        throw new GuiguException(201, "订单正在被支付");
    }
}
